package middleware.p2p;

import java.io.StringWriter;
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;

import middleware.p2p.log.MiddlewareMonitorEvent;
import middleware.p2p.log.QueueType;
import middleware.p2p.log.SerializableXmlString;
import middleware.p2p.log.XmlSerializable;

/**
 * 	Classe che si occupa di gestire una coda di Workflow sincronizzata tra più thread.
 * 	I metodi enqueue e dequeue si occupano di gestire la concorrenza con un modello produttore-consumatore.
 */
@XmlRootElement(name="wfqueue")
@XmlType(propOrder={"wfQueue"})
public class WFQueueManager implements WfQueue,XmlSerializable {
	/**
	 * 	Campo necessario alla serializzazione.
	 */
	private static final long serialVersionUID = 388077344386034697L;
	/**
	 * 	Coda che contiene i task.
	 */
	@XmlElementRef
    private LinkedList<Workflow> wfQueue;
	/**
	 * 	Massimo numero di elementi nella coda.
	 */
	private final int maxElement;
	/**
	 * 	Lock sulla coda. 
	 */
	@XmlTransient
	private final Lock lock;
	/**
	 * 	Condition variable sulla coda non vuota.
	 */
	@XmlTransient
	private final Condition notEmpty;
	/**
	 * 	Condition variable sulla coda non piena
	 */
	@XmlTransient
	private final Condition notFull;
    /**
     * 	Numero totale di elementi inseriti nella coda.
     */
	@XmlTransient
    private int totalElement;
    /**
	 * 	Costruttore di default della classe.
	 * 	Questo costruttore crea una nuova coda vuota.
	 */
    public WFQueueManager() {
    	this.totalElement=0;
    	this.lock= new ReentrantLock();
    	this.notEmpty=lock.newCondition();
    	this.notFull=lock.newCondition();
    	this.maxElement=Integer.MAX_VALUE;
        this.wfQueue=new LinkedList<Workflow>();
    }
    /**
     * 	Metodo per tornare la coda di workflow
     * 	@return La coda di workflow wfQueue
     */
    @XmlTransient
    public LinkedList<Workflow> getWfQueue() {
        return wfQueue;
    }
    /**
     * 	Metodo per impostare la coda di workflow con i valori passati
     * 	@param val La coda di workflow da impostare.
     */    
    public void setWfQueue(LinkedList<Workflow> val) {
        this.wfQueue = val;
    }
    /**
     * 	Metodo per ottenere il numero totale di elementi inseriti nella coda
	 * 	@return	Numero totale di elementi inseriti nella coda
	 */
    @XmlTransient
	public int getTotalElement() {
		return totalElement;
	}
	/**
	 * 	Metodo per ottenere il numero di elementi attualmente nella coda.
	 * 	@return	Numero di elementi attualmente in coda.
	 */
    @XmlTransient
	public int getQueueElement() {
		return this.wfQueue.size();
	}
	/**
	 * 	Metodo per estrarre dalla coda un Workflow
	 * 	Questo metodo si occupa di gestire la concorrenza fornendo l'accesso ai consumatori
	 * 	@return	Il Workflow estratto dalla coda.
	 */
    @Override
    public Workflow dequeue() {
    	return dequeue(new MiddlewareMonitorEvent());
    }
    /**
	 * 	Metodo per estrarre dalla coda un Workflow e ottenere dati sullo stato della coda
	 * 	Questo metodo si occupa di gestire la concorrenza fornendo l'accesso ai consumatori
	 * 	@param	event	Oggetto contenente i dati sulla coda.
	 * 	@return	Il Workflow estratto dalla coda.
	 */
    public Workflow dequeue(MiddlewareMonitorEvent event) {
        try {
        	this.lock.lock();
        	try {
        		while (this.wfQueue.size()==0) {
        			this.notEmpty.await();
        		}
        		Workflow wf=this.wfQueue.poll();
        		if (event==null) event=new MiddlewareMonitorEvent();
        		event.setQueueTotalElements(this.totalElement);
        		event.setQueueSize(this.wfQueue.size());
        		event.setQueueContent(new SerializableXmlString(this.toXmlString(),
        				"Logging Workflow Queue Content after dequeuing of Workflow #"+wf.getID()));
        		event.setType(QueueType.WFQueue);
        		notFull.signal();        		
        		return wf;
        	} finally {
        		this.lock.unlock();
        	}
		} catch (InterruptedException ie) {
			return null;
		}
    }
    /**
	 * 	Metodo per inserire un nuovo Workflow in coda.
	 * 	Questo metodo si occupa di gestire la concorrenza fornendo l'accesso al produttore.
	 * 	@param	wf	Workflow da inserire nella coda.
	 * 	@return	Il numero di elementi inseriti in coda
	 */    
    @Override
    public int enqueue(Workflow wf) {
    	return enqueue(wf, new MiddlewareMonitorEvent());
    }
    /**
     * 	Metodo per inserire un nuovo Workflow in coda e ottenere uno snapshot dello stato della coda.
     * 	@param wf		Il Workflow da inserire nella coda
     * 	@param event	Un oggetto contenente i dati sullo stato della coda subito dopo l'inserimento.	
     * 	@return			Il numero di elementi inseriti in coda
     */
    public int enqueue(Workflow wf,MiddlewareMonitorEvent event) {
        try {
        	this.lock.lock();
        	try {
        		while (this.wfQueue.size()==this.maxElement) {
        			notFull.await();
        		}
        		this.wfQueue.add(wf);
        		this.totalElement++;
        		if (event==null) event=new MiddlewareMonitorEvent();
        		event.setQueueTotalElements(this.totalElement);
        		event.setQueueSize(this.wfQueue.size());
        		event.setQueueContent(new SerializableXmlString(this.toXmlString(),
        				"Logging Workflow Queue Content after enqueuing of Workflow #"+wf.getID()));
        		event.setType(QueueType.WFQueue);
        		notEmpty.signal();
    			return 1;
        	} finally {
        		lock.unlock();
        	}
        } catch (InterruptedException ie) {
            return 0;
        }
    }
	@Override
	public String toXmlString() {
		try {
			JAXBContext context= JAXBContext.newInstance(this.getClass());
			Marshaller marshaller=context.createMarshaller();
			marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
			marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
			StringWriter sw=new StringWriter();			
			marshaller.marshal(this, sw);
			return sw.toString();
		} catch (JAXBException jaxbe) {
			jaxbe.printStackTrace();
			return "";
		}
	}
	/**
	 * 	Metodo statico per testare la concorrenza.
	 * 	@param args
	 */
	public static void main(String args[]) {
		final WFQueueManager queue= new WFQueueManager();
		Thread producer=new Thread(new Runnable() {			
			public void run() {
				int k=0;
				while(true) {
					//if ((k%2==0)&&(k>4)) {
						try {
							Thread.sleep(5000);
						} catch (InterruptedException e) {
						}
					//}
					k++;
					Workflow wf=new Workflow();
					wf.setID(k);
					wf.addTask(new TaskDescriptor(k, TaskTYPE.Executable, "", ""));
					queue.enqueue(wf);
					System.out.println("Inserito workflow "+k+" nella coda");
				}
			}
		});
		producer.start();
		Thread cons1=new Thread(new Runnable() {			
			public void run() {
				while(true) {
					System.out.println("CONS1 ==> Tento di estrarre un elemento dalla coda...");
					Workflow wf=queue.dequeue();
					System.out.println("CONS1 ==> Elemento prelevato dalla coda.ID = "+wf.getID());
					try {
						Thread.sleep(2000);
					} catch (InterruptedException e) {
					}
				}				
			}
		});
		Thread cons2=new Thread(new Runnable() {			
			public void run() {
				while(true) {
					System.out.println("CONS2 ==> Tento di estrarre un elemento dalla coda...");
					Workflow wf=queue.dequeue();
					System.out.println("CONS2 ==> Elemento prelevato dalla coda.ID = "+wf.getID());
					try {
						Thread.sleep(4000);
					} catch (InterruptedException e) {
					}
				}				
			}
		});
		cons1.start();
		cons2.start();
	}
}
